#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"

//enable irq0 irq1 irq2 irq14
#define IMR_FLAG 0xbff8
#define EFLAGS_IF_MASK 0x200
#define IDT_CNT 0x81
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; pop %0":"=g"(EFLAG_VAR))
extern intr_handler IDT_ENTRY_TAB[IDT_CNT];
extern void syscall_entry(uint8_t vec_no);

intr_handler idt_table[IDT_CNT];

char* intr_name[IDT_CNT]={
	"#DE Divide Error",
	"#DB Debug Exception",
	"#NMI Interrupt",
	"#BP Breakpoint Exception",
	"#OF Overflow Exception",
	"#BR Bound Rage Exceeded Exception",
	"#UD Invalid Opcode Exception",
	"#NM Device Not Available Exception",
	"#DF Double Fault Exception",
	"Coprocessor Segment Overrun",
	"#TS Invalid TSS Exception",
	"#NP Segment Not Present",
	"#SS Stack Fault Exception",
	"#GP General Protection Exception",
	"#PF Page Falut Exception",
	"#Reserver",
	"#MF FPU Floating Point Error",
	"#AC Alignment Check Exception",
	"#MC Machine Check Exception",
	"#XF SIMD Floating Point Exception",
};

typedef struct {
	uint16_t intr_offset_low;
	uint16_t intr_sel;
	uint8_t  revers;
	uint8_t  attrs;
	uint16_t intr_offset_hi;
}idt_gate_des;

idt_gate_des idt_des_table[IDT_CNT];

static void make_idt_gate_des(idt_gate_des* p_gate_des,uint16_t sel,intr_handler entry,uint8_t attrs){
	p_gate_des->intr_offset_low= (uint32_t)entry & 0xffff;
	p_gate_des->intr_sel=sel;
	p_gate_des->attrs=attrs;
	p_gate_des->intr_offset_hi= (uint32_t)entry>>16 & 0xffff;
}

//here we make idt gate describetor
static void init_idt_gate_des(void){
	uint32_t i;
	for(i=0;i<IDT_CNT;i++){
		make_idt_gate_des(&idt_des_table[i],K_CODE_SEL,IDT_ENTRY_TAB[i],GATE_DES_ATTR);
	}
	//we use interrupt 0x80 as our system call
	make_idt_gate_des(&idt_des_table[0x80],K_CODE_SEL,syscall_entry,SYSCALL_DES_ATTR);
	uint64_t idt_operand;
	idt_operand=((sizeof(idt_des_table)-1) | ((uint64_t)idt_des_table<<16));
	asm volatile("lidt %0"::"m"(idt_operand));
}

static void general_exception_handler(uint8_t vec_nm){
	int32_t i;
	set_cursor(0);
	for(i=0;i<4*TEXT_MODE_LINE_ROW;i++){
		put_char(' ');	
	}
	set_cursor(0);
	put_str("vec_nm:");
	put_int(vec_nm);
	put_char('\n');
	put_str(intr_name[vec_nm]);
	put_char('\n');
	while(1);
}

static void exception_init(void){
	int i;
	for(i=0;i<IDT_CNT;i++){
		idt_table[i]=(intr_handler)general_exception_handler;
		if(i>=20&&i<=31)
			intr_name[i]="reverse";
	}
	intr_name[0x20]="timer interrupt";
	intr_name[0x21]="keyboard interrupt";
}


//here we inition the 8259A
static void init_pic(void){
	outb(0x11,PIC_M_CTRL);
	outb(0x20,PIC_M_DATA);
	outb(0x04,PIC_M_DATA);
	outb(0x01,PIC_M_DATA);

	outb(0x11,PIC_S_CTRL);
	outb(0x28,PIC_S_DATA);
	outb(0x02,PIC_S_DATA);
	outb(0x01,PIC_S_DATA);
	// for test only timer irq
	outb(IMR_FLAG & 0xff,PIC_M_DATA);
	outb(IMR_FLAG>>8 & 0xff,PIC_S_DATA);
	put_str("pic init done\n");
}

void init_interrupt(void){
	put_str("init interrupt\n");
	init_idt_gate_des();
	exception_init();
	init_pic();
}


intr_status intr_get_status(void){
	uint32_t eflag;
	GET_EFLAGS(eflag);
	return eflag&EFLAGS_IF_MASK ? INTR_ON : INTR_OFF;	
}

intr_status intr_set_status(intr_status s){
	return s & INTR_ON ? intr_enable() : intr_disable(); 
}

intr_status intr_enable(void){
	intr_status old_status=intr_get_status();
	if(old_status!=INTR_ON){
		asm volatile("sti");
	}
	return old_status;
}

intr_status intr_disable(void){
	intr_status old_status=intr_get_status();
	if(old_status!=INTR_OFF){
		asm volatile("cli");
	}
	return old_status;
}

void register_intr_handler(uint8_t vec_no,intr_handler h){
	ASSERT(vec_no<IDT_CNT);
	idt_table[vec_no]=h;
}
